package com.jackstraw.jack_base.rules.service.impl;


import com.jackstraw.jack_base.rules.config.DroolsUtil;
import com.jackstraw.jack_base.rules.entity.bo.RelationSargBo;
import com.jackstraw.jack_base.rules.entity.bo.RuleGroupBo;
import com.jackstraw.jack_base.rules.entity.bo.SceneActuatorBo;
import com.jackstraw.jack_base.rules.entity.dto.RuleExcuteDto;
import com.jackstraw.jack_base.rules.entity.dto.RuleExecutionObject;
import com.jackstraw.jack_base.rules.entity.enums.ResultEnum;
import com.jackstraw.jack_base.rules.exception.BadRuleException;
import com.jackstraw.jack_base.rules.service.RelationSargService;
import com.jackstraw.jack_base.rules.service.RuleBizService;
import com.jackstraw.jack_base.rules.service.RuleExcuteService;
import com.jackstraw.jack_base.rules.service.RuleGroupService;
import com.jackstraw.jack_base.rules.service.SceneActuatorService;
import com.jackstraw.jack_base.rules.threadpool.ExcuteInitThreadPool;
import com.jackstraw.jack_base.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.drools.core.base.RuleNameStartsWithAgendaFilter;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Slf4j
@Component
public class RuleBizServiceImpl implements RuleBizService {

    @Autowired
    private SceneActuatorService sceneActuatorService;

    @Autowired
    private RelationSargService relationSargService;

    @Autowired
    private RuleGroupService ruleGroupService;

    /**
     * 锁机制
     */
    private final Map<String, Lock> lockMap = new HashMap<>();

    @Override
    public RuleExcuteDto excute(RuleExcuteDto ruleExcuteDto) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("**************************执行场景[{}]开始*************************************************",
                ruleExcuteDto.getSceneTypeEnum().getRuleName());
        RuleExcuteService ruleExcuteService = this.ruleExcuteService(ruleExcuteDto.getSceneTypeEnum().getRuleName());
        if (ObjectUtils.isEmpty(ruleExcuteService)) {
            log.error("没有找到对应的场景[{}]执行器", ruleExcuteDto.getSceneTypeEnum().getRuleName());
            throw new BadRuleException(ResultEnum.SCENE_DATA_NO_FIND_BY_DATABASE);
        }
        ruleExcuteService.excute(ruleExcuteDto.getSceneTypeEnum().getRuleName(), ruleExcuteDto.getBody());
        stopWatch.stop();
        log.info("**************************执行场景[{}]结束,共耗时[{}Millis]***************************************************",
                ruleExcuteDto.getSceneTypeEnum().getRuleName(), stopWatch.getTime());
        return ruleExcuteDto;
    }

    @Override
    public RuleExcuteService ruleExcuteService(String sceneName) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("=================获取场景执行器[{}]开始=================", sceneName);
        Integer sceneVersion = sceneActuatorService.querySceneVersion(sceneName);
        //   0 理论上不存在
        //   -1 数据不存在
        //   -2 场景失效
        if (sceneVersion <= 0) {
            DroolsUtil.getInstance().clearSceneMap(sceneName);
            return null;
        }
        Integer sceneVersionInCache = DroolsUtil.getInstance().getSceneVersionInCache(sceneName);
        if (ObjectUtils.isEmpty(sceneVersionInCache)
                || sceneVersionInCache < sceneVersion) {
            String lockKey = "S".concat(sceneName);
            Lock lock = lockMap.get(lockKey);
            if (ObjectUtils.isEmpty(lock)) {
                lock = new ReentrantLock();
                lockMap.put(lockKey, lock);
            }
            try {
                //尝试获取锁三分钟,获取不到则抛出异常
                if (lock.tryLock(3L, TimeUnit.MINUTES)) {
                    try {
                        sceneVersionInCache = DroolsUtil.getInstance().getSceneVersionInCache(sceneName);
                        if (ObjectUtils.isEmpty(sceneVersionInCache)
                                || sceneVersionInCache < sceneVersion) {
                            //生成RuleExcuteService
                            SceneActuatorBo sceneActuatorBo = sceneActuatorService.queryOneWithBlob(sceneName);
                            List<RelationSargBo> relationSargBos = relationSargService.queryAllBySceneId(sceneActuatorBo.getSceneActuatorId());
                            sceneActuatorBo.setRuleGroupIdS(relationSargBos.stream()
                                    .map(RelationSargBo::getRuleGroupId)
                                    .collect(Collectors.toList()));
                            this.ruleExcuteService(sceneActuatorBo);
                        }
                    } finally {
                        //只有拿到锁了才能解锁
                        lock.unlock();
                    }
                } else {
                    log.error("初始化场景执行器[{}]时获取锁超时", sceneName);
                    throw new BadRuleException("000", "系统异常，请重试");
                }
            } catch (InterruptedException e) {
                log.error("初始化场景执行器[{}]时获取锁异常", sceneName, e);
                throw new BadRuleException("000", "系统异常，请重试", e);
            }
        }
        stopWatch.stop();
        log.info("=================获取场景执行器[{}]结束,共耗时[{}Millis]=================", sceneName, stopWatch.getTime());
        return DroolsUtil.getInstance().getSceneExcuteServiceInCache(sceneName);
    }

    @Override
    public void ruleExcuteService(SceneActuatorBo sceneActuatorBo) {
        RuleExcuteService ruleExcuteService
                = DroolsUtil.getInstance().ruleExcuteService(sceneActuatorBo);
        //初始化对应规则组
        List<Long> ruleGroupIdS = ruleExcuteService.getRuleGroupIdS();
        if (ruleGroupIdS.size() > 0) {

            //等待执行完成后在返回
            CountDownLatch countDownLatch = new CountDownLatch(ruleGroupIdS.size());
            ruleGroupIdS.stream().forEach(ruleGroupId -> {
                //线程池执行任务
                ExcuteInitThreadPool.submitTask(() -> {
                    try {
                        this.kieSession(ruleGroupId);
                    } finally {
                        countDownLatch.countDown();
                    }
                });
            });
            //等待三分钟，即使是没有成功也没有关系  后续用到的时候如果没有找到会自行初始化
            try {
                countDownLatch.await(3L, TimeUnit.MINUTES);
            } catch (InterruptedException e) {
                log.warn("场景初始化对应规则组时等待超时，超时原因为:", e);
            }
        }
    }

    @Override
    public KieSession kieSession(Long ruleGroupId) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        log.info("=================获取规则组Session[{}]开始=================", ruleGroupId);
        Integer groupVersion = ruleGroupService.queryGroupVersion(ruleGroupId);
        //   0 理论上不存在
        //   -1 数据不存在
        //   -2 规则组失效
        if (groupVersion <= 0) {
            DroolsUtil.getInstance().clearRuleGroupMap(ruleGroupId);
            return null;
        }
        Integer groupVersionInCache = DroolsUtil.getInstance().getVersionInCache(ruleGroupId);
        if (ObjectUtils.isEmpty(groupVersionInCache)
                || groupVersionInCache < groupVersion) {
            String lockKey = "G".concat(String.valueOf(ruleGroupId));
            Lock lock = lockMap.get(lockKey);
            if (ObjectUtils.isEmpty(lock)) {
                lock = new ReentrantLock();
                lockMap.put(lockKey, lock);
            }

            //尝试获取锁10S,获取不到则抛出异常
            try {
                if (lock.tryLock(10L, TimeUnit.SECONDS)) {
                    try {
                        groupVersionInCache = DroolsUtil.getInstance().getVersionInCache(ruleGroupId);
                        if (ObjectUtils.isEmpty(groupVersionInCache)
                                || groupVersionInCache < groupVersion) {
                            //生成kieBase
                            RuleGroupBo ruleGroupBo = ruleGroupService.queryOneAll(ruleGroupId);
                            DroolsUtil.getInstance().kieBase(ruleGroupBo);
                        }
                    } finally {
                        //只有拿到锁了才能解锁
                        lock.unlock();
                    }
                } else {
                    log.error("编译规则组[{}]文件时获取锁超时", ruleGroupId);
                    throw new BadRuleException("000", "系统异常，请重试");
                }
            } catch (InterruptedException e) {
                log.error("编译规则组[{}]文件时获取锁异常", ruleGroupId, e);
                throw new BadRuleException("000", "系统异常，请重试", e);
            }
        }
        stopWatch.stop();
        log.info("=================获取规则组Session[{}]结束,共耗时[{}Millis]=================", ruleGroupId, stopWatch.getTime());
        return DroolsUtil.getInstance().getKieSessionInCache(ruleGroupId);
    }

    @Override
    public RuleExecutionObject executeGroup(Long ruleGroupId, RuleExecutionObject ruleExecutionObject) {
        KieSession kieSession = this.kieSession(ruleGroupId);
        if (ObjectUtils.isEmpty(kieSession)) {
            log.error("没有找到对应的规则组[{}]配置", ruleGroupId);
            throw new BadRuleException(ResultEnum.RULE_GROUP_DATA_NO_FIND_BY_DATABASE);
        }
        try {
            // 1.插入全局对象
            Map<String, Object> globalMap = ruleExecutionObject.getGlobalMap();
            for (Object glb : globalMap.entrySet()) {
                Map.Entry global = (Map.Entry) glb;
                String key = (String) global.getKey();
                Object value = global.getValue();
                log.info("插入Global对象:" + value.getClass().getName());
                kieSession.setGlobal(key, value);
            }
            // 2.插入fact对象
            // 2.1插入业务fact对象
            ruleExecutionObject.getFactObjectMap().keySet().stream()
                    .forEach(fullClassName -> {
                        log.info("插入Fact对象：" + fullClassName);
                        kieSession.insert(ruleExecutionObject.getFactObjectMap().get(fullClassName));
                    });
            // 2.2将 ruleExecutionObject 也插入到规则中，回调使用
            kieSession.insert(ruleExecutionObject);

            //执行规则
            //是否全部执行
            if (StringUtils.isNotEmpty(ruleExecutionObject.getGroupName())) {
                //如果分组名称不为空则执行某个组的规则
                kieSession.getAgenda()
                        .getAgendaGroup(ruleExecutionObject.getGroupName())
                        .setFocus();
            }
            if (StringUtils.isNotEmpty(ruleExecutionObject.getRuleName())) {
                //执行某个规则开头的文件
                RuleNameStartsWithAgendaFilter ruleNameStartsWithAgendaFilter = new RuleNameStartsWithAgendaFilter(ruleExecutionObject.getRuleName());
                kieSession.fireAllRules(ruleNameStartsWithAgendaFilter);
            } else {
                kieSession.fireAllRules();
            }
        } finally {
            kieSession.dispose();
        }
        return ruleExecutionObject;
    }

    @Override
    public void init() {
        //初始化场景任务
        //查询所有有效的场景任务
        List<SceneActuatorBo> sceneActuatorBos = sceneActuatorService.queryAllRules();
        if (CollectionUtil.isEmpty(sceneActuatorBos)) {
            log.warn("系统内未配置场景");
            return;
        }
        CountDownLatch countDownLatch = new CountDownLatch(sceneActuatorBos.size());

        sceneActuatorBos.stream().forEach(sceneActuatorBo -> {
            //线程池执行任务
            ExcuteInitThreadPool.submitTask(() -> {
                try {
                    List<RelationSargBo> relationSargBos = relationSargService.queryAllBySceneId(sceneActuatorBo.getSceneActuatorId());
                    sceneActuatorBo.setRuleGroupIdS(relationSargBos.stream()
                            .map(RelationSargBo::getRuleGroupId)
                            .collect(Collectors.toList()));
                    ruleExcuteService(sceneActuatorBo);
                } finally {
                    countDownLatch.countDown();
                }
            });
        });

        //等待三分钟，即使是没有成功也没有关系  后续用到的时候如果没有找到会自行初始化
        try {
            countDownLatch.await(3L, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            log.warn("项目启动时初始化场景执行器超时");
        }
    }
}
